﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace XmlVisualizer.DiagramDesigner
{
    /// <summary>
    /// Enumeration of connector orientations.
    /// </summary>
    public enum ConnectorOrientation
    {
        None,
        Left,
        Top,
        Right,
        Bottom
    }

    /// <summary>
    /// Provides compact information about a connector; used for the 
    /// routing algorithm, instead of handing over a full Connector.
    /// </summary>
    internal struct ConnectorInfo
    {
        /// <summary>
        /// Gets or sets the left coordinate of DesignerItem.
        /// </summary>
        public double DesignerItemLeft { get; set; }

        /// <summary>
        /// Gets or sets the top coordinate of DesignerItem.
        /// </summary>
        public double DesignerItemTop { get; set; }

        /// <summary>
        /// Gets or sets the DesignerItem's size.
        /// </summary>
        public Size DesignerItemSize { get; set; }

        /// <summary>
        /// Gets or sets the DesignerItem's position.
        /// </summary>
        public Point Position { get; set; }

        /// <summary>
        /// Gets or sets the DesignerItem's orientation.
        /// </summary>
        public ConnectorOrientation Orientation { get; set; }
    }

    /// <summary>
    /// Connector class. Represents connector for DesignerItem. 
    /// </summary>
    public class Connector : Control, INotifyPropertyChanged
    {
        #region Fields
        // drag start point, relative to the DesignerCanvas
        private Point? dragStartPoint = null;

        // center position of this Connector relative to the DesignerCanvas
        private Point position;

        // the DesignerItem this Connector belongs to;
        // retrieved from DataContext, which is set in the
        // DesignerItem template
        private DesignerItem parentDesignerItem;

        // keep track of connections that link to this connector
        private List<Connection> connections;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Connector class.
        /// </summary>
        public Connector()
        {
            // fired when layout changes
            this.LayoutUpdated += new EventHandler(this.Connector_LayoutUpdated);
        }
        #endregion

        /// <summary>
        /// PropertyChanged event.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #region Properties
        /// <summary>
        /// Gets or sets the connector's orientation.
        /// </summary>
        public ConnectorOrientation Orientation { get; set; }

        /// <summary>
        /// Gets or sets the connector's position.
        /// </summary>
        public Point Position
        {
            get
            {
                return this.position;
            }

            set
            {
                if (this.position != value)
                {
                    this.position = value;
                    this.OnPropertyChanged("Position");
                }
            }
        }

        /// <summary>
        /// Gets the DesignerItem this Connector belongs to.
        /// </summary>
        public DesignerItem ParentDesignerItem
        {
            get
            {
                if (this.parentDesignerItem == null)
                {
                    this.parentDesignerItem = this.DataContext as DesignerItem;
                }

                return this.parentDesignerItem;
            }
        }

        /// <summary>
        /// Gets the list of connections linking to this connector.
        /// </summary>
        public List<Connection> Connections
        {
            get
            {
                if (this.connections == null)
                {
                    this.connections = new List<Connection>();
                }

                return this.connections;
            }
        }
        #endregion

        /// <summary>
        /// Returns information about the connector's position, orientation and parent designer item's position.
        /// </summary>
        /// <returns>Filled ConnectorInfo.</returns>
        internal ConnectorInfo GetInfo()
        {
            ConnectorInfo info = new ConnectorInfo();
            info.DesignerItemLeft = DesignerCanvas.GetLeft(this.ParentDesignerItem);
            info.DesignerItemTop = DesignerCanvas.GetTop(this.ParentDesignerItem);
            info.DesignerItemSize = new Size(this.ParentDesignerItem.ActualWidth, this.ParentDesignerItem.ActualHeight);
            info.Orientation = this.Orientation;
            info.Position = this.Position;
            return info;
        }

        #region Event handlers
        /// <summary>
        /// Invoked on ProperyChanged event.
        /// </summary>
        /// <param name="name">Property name.</param>
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        /// <summary>
        /// Invoked when the user presses the left mouse button over the connector.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            DesignerCanvas canvas = this.GetDesignerCanvas(this);
            if (canvas != null)
            {
                // position relative to DesignerCanvas
                this.dragStartPoint = new Point?(e.GetPosition(canvas));
                e.Handled = true;
            }
        }

        /// <summary>
        /// Invoked when the user moves the cursor over the connector.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            // if mouse button is not pressed we have no drag operation, ...
            if (e.LeftButton != MouseButtonState.Pressed)
            {
                this.dragStartPoint = null;
            }

            // but if mouse button is pressed and start point value is set we do have one
            if (this.dragStartPoint.HasValue)
            {
                // create connection adorner 
                DesignerCanvas canvas = this.GetDesignerCanvas(this);
                if (canvas != null)
                {
                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                    if (adornerLayer != null)
                    {
                        ConnectorAdorner adorner = new ConnectorAdorner(canvas, this);
                        if (adorner != null)
                        {
                            adornerLayer.Add(adorner);
                            e.Handled = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Invoked when the layout changes.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        private void Connector_LayoutUpdated(object sender, EventArgs e)
        {
            DesignerCanvas designer = this.GetDesignerCanvas(this);
            if (designer != null)
            {
                // get centre position of this Connector relative to the DesignerCanvas
                this.Position = this.TransformToAncestor(designer).Transform(new Point(this.Width / 2, this.Height / 2));
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Iterates through the visual tree to get the parent DesignerCanvas.
        /// </summary>
        /// <param name="element">Dependency object which parent to look for.</param>
        /// <returns>Reference to DesignerCanvas or null if not found.</returns>
        private DesignerCanvas GetDesignerCanvas(DependencyObject element)
        {
            while (element != null && !(element is DesignerCanvas))
            {
                element = VisualTreeHelper.GetParent(element);
            }

            return element as DesignerCanvas;
        }
        #endregion
    }
}